一、从ZDI文章开始
2018年4月,ZDI发表了《INVERTING YOUR ASSUMPTIONS: A GUIDE TO JIT COMPARISONS》,描述了JavaScriptCore DFG JIT中CompareEq IR的副作用问题。通过TenSec2018的ppt,可以知道这个漏洞编号为CVE-2018-4162。
从文章给出的补丁上看,这显然是一个副作用问题:
1 | function primitiveFakeObj(addr) { |
c == 1会触发一些回调,回调的内容就是{toString: () => { arr[0] = {}; return ‘1’; }}这个对象的toString函数。本次DFG代码中的回调调用了baseline JIT中的operationCompareEq:
1 | // v1 = object |
…
从此以后,go(arr, {})、go(arr, {toString: () => { arr[0] = {}; return ‘1’; }})将会走向不同的地方。前者会走向objectProtoFuncValueOf,后者会走向其定义的toString函数。
实际调试发现,除了DFGAbstractInterpreter的clobberWorld之外,还会插入InvalidationPoint,那么也与DFGClobberize有关了。该版本下CompareEq是这样的:
其中isBinaryUseKind意思就是isBothUseKind,而根据PoC,DFG生成的代码:
1 | 31:<!1:loc8> CompareEq(Untyped:@30, Untyped:@27, Boolean|MustGen|PureInt, Bool, R:World, W:Heap, Exits, ClobbersExit, bc#17, ExitValid) |
会插入InvalidationPoint和Jump replacement,显然无法复现。
二、补丁寻找
翻近一年多的commit,看到关于CompareEq有这么几处补丁:
https://github.com/WebKit/webkit/commit/b6b0023ff9a7a327bcbd6c1badaaea459d650235
其中后两个修来修去没啥实际效果(后文提到),第一个是符合ZDI文章的补丁:
参考CVE-2018-4233,这个补丁导致的结果是CheckStructure/CheckArray等。究竟能不能利用,必须要求Invalidation不插入,也就是Clobberize定义出问题。但是在这个版本以及往前很多的版本下,DFGClobberize定义的CompareEq仍然是这样的:
于情于理,确实无法复现。
好在有同事在某不知名版本上复现了这个漏洞,反编译结果大概形如:
那么与之接近的源码应该为:
虽然仍然找不到具体哪个版本用了这样的代码,但漏洞终于可以复现了。
三、成因分析
3.1 CompareEq的UseKind决定机器码内容
不管是DFGAbstractInterpreter还是DFGClobberize,出现问题的原因涉及CompareEq的children的UseKind。根据child1、child2的useKind,DFG在生成CompareEq的IR结点时,会有类似:
1 | 31:<!1:loc8> CompareEq(Untyped:@30, Untyped:@27, Boolean|MustGen|PureInt, Bool, R:World, W:Heap, Exits, ClobbersExit, bc#17, ExitValid) |
其中Untyped、Object对应两种UseKind。根据CompareEq的UseKind,会生成不同的机器码:
1 | bool SpeculativeJIT::compilePeepHoleBranch(Node* node, MacroAssembler::RelationalCondition condition, MacroAssembler::DoubleCondition doubleCondition, S_JITOperation_EJJ operation) |
可以把PoC改一下,针对性地修改UseKind,可以看到UseKind为Object、StringIdent时,生成的机器码不含有回调的:
1 | //ObjectUse -- no callback |
1 | //StringIdent -- no callback |
1 | bool SpeculativeJIT::compare(Node* node, MacroAssembler::RelationalCondition condition, MacroAssembler::DoubleCondition doubleCondition, S_JITOperation_EJJ operation) |
3.2 CompareEq的UseKind由DFGFixupPhase确定
根据源码,CompareEq的默认UseKind都是Untyped,这一步由DFGByteCodeParser决定:
UseKind在fixup phase里可以被修改,寻找线索的三种方法:
- 搜索“ = UntypedUse”
- 使用watchpoint调试跟踪
- dumpGraphAtEachPhase
其中泛型UseKind11是ObjectUse。
而代码要求,必须child1、child2的SpeculatedType均为object时才会设置UseKind为ObjectUse。
1 | bool shouldSpeculateObject() |
四、总结
DFG JIT的实现代码犹如草蛇灰线,伏脉千里。安全研究员提出修补建议、官方修补漏洞时都可能忽略一些问题,造成修补的反复进行。
在3.1节中,IR结点的UseKind决定编译结果是否含有回调,因而不妨作为“DFG回调副作用”这一pattern挖掘的核心和Entry Point。